//============================================================================
// Name        : Ricart.cpp
// Author      : Husheng Zhou
// Version     :
// Copyright   : The file is distributed under the License of Lab for System and Software Security at UTD (S^3 lab). All rights reserved.
// Description : Implement Ricart's Algorithm.
//============================================================================

#include "Ricart.h"

void Proc::Initialization() {
	// Input file name.
	string configFileName = "config.txt";
	cout << "Read config file ./" << configFileName << endl;

	// Read input files.
	readFile(configFileName, _config);
	cout << "===========================" << endl;
	list<Host> all;
	list<string> l_name;

	for (int i = 0; i < 3; i++) {
		Host one;
		one.name = _config[0][i * 2];
		one.port = strToInt(_config[0][i * 2 + 1]);
		all.push_back(one);
		l_name.push_back(one.name);
	}


	_id = whoami(l_name);
	_comm = Communication(all, false);


//	cout<<"enter faked name"<<endl;
//	int me;
//	cin>>me;
//	_id=me;
//	_comm = Communication(all, false,me);

}
//Ensure that all messages should be delivered at now were delivered
//Receive only one message
int Proc::wait_recv(Message &msg, unsigned int now)
{
	int tag=0;
	int count=0;
	while(true){
	tag = _comm.recv(msg, now);
	if(tag<=-1)
		return tag;
	}
}

//Receive all delivered messages.
list<Message> Proc::wait_recv_all(unsigned int now) {
	list<Message> l_recv;
	Message msg;
	int remain_length = -1 - _comm.recv(msg, now);
	l_recv.push_back(msg);
	if (remain_length > 0) {
		for (int i = 0; i < remain_length; i++) {
			Message m;
			_comm.recv(m, now);
			l_recv.push_back(m);
		}

	}
	return l_recv;
}

//deliver all messages
list<Message> Proc::wait_deliv_all(unsigned int now) {
	list<Message> l_deliv;
	Message msg;
	while (_comm.recv(msg, now) <= -1) {
		l_deliv.push_back(msg);
	}
	return l_deliv;
}

//Wait till all messages to current process were sent(not delivered)
void Proc::wait_sent2me(Message &msg, unsigned int now,
		unsigned int num_sent2me) {
	while (_comm.recv(msg, now) != num_sent2me) {

	}
	return;
}

//send log to server, at the same time print the info on console
void Proc::send_log(string info) {
	Message msg;
	msg.node = -2;
	msg.data = "m" + info;
	msg.deliv = 0;
	_comm.send(msg);
	cout << info << endl;

}

void Proc::send_finish() {
	Message msg;
	msg.node = -2;
	msg.data = "f";
	msg.deliv = 0;
	_comm.send(msg);
}

//send termination
void Proc::send_term() {
	Message msg;
	msg.node = -2;
	msg.data = "t";
	msg.deliv = 0;
	_comm.send(msg);
}

void Proc::send_num_sent(int n0, int n1, int n2) {
	Message msg;
	msg.node = -2;
	msg.data = "s";
	//msg.data.replace(id+1,1,intToStr(n));
	msg.data += intToStr(n0) + intToStr(n1) + intToStr(n2);
	msg.deliv = 0;
	_comm.send(msg);
}

void Proc::send_num_sent(vector<int> v_sent) {
	Message msg;
	msg.node = -2;
	msg.data = "s";
	//msg.data.replace(id+1,1,intToStr(n));
	msg.data += intToStr(v_sent[0]) + intToStr(v_sent[1]) + intToStr(v_sent[2]);
	msg.deliv = 0;
	_comm.send(msg);
}

//Learn how many messages should be sent to current process
//tag[0]=recv_tag; tag[1]=the num I should receive
int Proc::get_num_sent2me(unsigned int now) {
	int tag;
	Message msg;
	wait_recv(msg, now);
	string type = msg.data.substr(0, 1);
	if (type == "r") {
		tag = strToInt(msg.data.substr(_id + 1, 1));
	} else {
		tag = 0;
		cout << "I need the information of r(how many messages sent to me)"<< msg.data<<"---"<<msg.node<<"----"<<msg.deliv<<endl;
	}
	return tag;
}

//Receive and deliver signal "next" from monitor, and immediately update current _tick_count.
bool Proc::update_tick_count() {
	Message msg;
	wait_recv(msg, _tick_count);
	string type = msg.data.substr(0, 1);
	if (type == "n") {
		_tick_count = strToInt(msg.data.substr(1));
		return true;
	} else {
		cout << "I need info of n" << endl;
		return false;
	}
}

int Proc::get_lower(int me) {
	int lower;
	switch (me) {
	case 0:
		lower = 1;
		break;
	case 1:
		lower = 0;
		break;
	case 2:
		lower = 1;
		break;
	}
	return lower;

}

int Proc::get_upper(int me) {
	int upper;
	switch (me) {
	case 0:
		upper = 2;
		break;
	case 1:
		upper = 2;
		break;
	case 2:
		upper = 1;
		break;
	}
	return upper;
}

//Read config file to send request to all other processes
string Proc::request(int me, unsigned now, vector<vector<string> > content) {
	string log;
	if (!_requesting_cs) {
		for (int i = 0; i < content.size(); i++) {
			if (content[i][0] == intToStr(me) && content[i][1] == "q"
					&& content[i][2] == intToStr(now)) {
				Message msg1;
				_seq_no = _highest_seq_no + 1;
				_outstanding_reply = 2;
				msg1.node = get_lower(me);
				msg1.deliv = strToInt(content[i][3]);
				msg1.data = "q," + intToStr(_seq_no) + "," + intToStr(_id);
				_comm.send(msg1);
				cout << "Send request to process " + intToStr(get_lower(me))
						<< endl;
				log = "Process " + intToStr(_id) + " send request to process "
						+ intToStr(get_lower(me)) + "\n";

				Message msg2;
				_seq_no = _highest_seq_no + 1;
				_outstanding_reply = 2;
				msg2.node = get_upper(me);
				msg2.deliv = strToInt(content[i + 1][3]);
				msg2.data = "q," + intToStr(_seq_no) + "," + intToStr(_id);
				_comm.send(msg2);
				cout << "Send request to process " + intToStr(get_upper(me))
						<< endl;
				log = "Process " + intToStr(_id) + " send request to process "
						+ intToStr(get_upper(me)) + "\n";
			}
			_requesting_cs = true;
			_outstanding_reply = 2;
		}
	} else {
		cout << "Already requested critical_section, can NOT send again"
				<< endl;
		log = "Process " + intToStr(_id)
				+ " already requested critical_section, can NOT send again\n";
	}
	return log;
}

//Reply immediately to the source who send request. data like this: "p,3" means type is reply, and the source is process 3
string Proc::reply(int node, unsigned deliv) {
	string log;
	Message msg;
	msg.node = node;
	msg.deliv = deliv;
	msg.data = "p";
	_comm.send(msg);
	cout << "Send reply to process " + intToStr(node) << endl;
	log = "Process " + intToStr(_id) + " send reply to process "
			+ intToStr(node) + "\n";
	return log;
}

vector<int> Proc::update_sent_vec(int to_node, vector<int> v_sent) {
	switch (to_node) {
	case 0:
		v_sent[0] = v_sent[0] + 1;
		break;
	case 1:
		v_sent[1] = v_sent[1] + 1;
		break;
	case 2:
		v_sent[2] = v_sent[2] + 1;
		break;
	}
	return v_sent;
}

void Proc::read_own_thing(string fileName, list< list<vector<string> > >& content,
		int me) {
	ifstream infile(fileName.c_str(), ios::in | ios::binary);
	if (!infile.is_open()) {
		cerr << "Error! can not open file " << fileName << " !" << endl;
		exit(EXIT_FAILURE);
	}
	string str;
	vector<string> own_line;
	list<vector<string> > l_quest;
	list<vector<string> > l_reply;
	list<vector<string> > l_cs;
	list<vector<string> > l_terminal;
	while (getline(infile, str)) {
		own_line = split(str, '\t');
		if (own_line[0] == intToStr(me)) {
			if (own_line[1] == "q") {
				l_quest.push_back(own_line);
			}
			if (own_line[1] == "p") {
				l_reply.push_back(own_line);
			}
			if (own_line[1] == "cs") {
				l_cs.push_back(own_line);
			}
			if (own_line[1] == "t") {
				l_terminal.push_back(own_line);
			}
		}
	}
	content.push_back(l_quest);
	content.push_back(l_reply);
	content.push_back(l_cs);
	content.push_back(l_terminal);
	infile.close();
}

int main() {
	cout << "Ricart Mutual Exclusion" << endl;
	int num_config = 0;
	int req_num = 0;
	int reply_num = 0;
	int cs_num = 0;
	int waiting_cs = -2; //after how many counts the c_s would finish. Once this num becomes 0, finish c_s
	int termination;
	bool is_in_cs = false;
	list<vector<string> > l_quest;
	list<vector<string> > l_reply;
	list<vector<string> > l_cs;
	vector<int> v_num_sent;
	v_num_sent.push_back(0);
	v_num_sent.push_back(0);
	v_num_sent.push_back(0);
	list<Message> l_tobe_sent; //replies of last tick_count
	list<Message> l_delivered;

	Proc proc;
	list< list< vector<string> > > content;
	proc.read_own_thing("config.txt", content, proc._id);

	//get my request action
	l_quest = content.front();
	content.pop_front();

	//get my reply action
	l_reply = content.front();
	content.pop_front();

	//get my c_s action
	l_cs = content.front();
	content.pop_front();

	//get terminal time
	termination=strToInt(content.front().front()[3]);

	while (termination>=proc._tick_count)
	{
		v_num_sent[0]=0;
		v_num_sent[1]=0;
		v_num_sent[2]=0;
		if (waiting_cs > 1)//still in critical_section
		{
			//next get "n" from server
			proc.update_tick_count();

			//c_s -- send log: still in c_s
			proc.send_log("Process " + intToStr(proc._id)+ " still in critical_section ");
			waiting_cs--;

			//sent num_sent=0 to server
			proc.send_num_sent(0,0,0);


			//receive num_recv from server
			while(true)
			{
				Message temp_msg;
				int temp_tag=proc.wait_recv(temp_msg,proc._tick_count);

				if(temp_tag<=-1)
				{
				if(temp_msg.data.substr(0, 1) == "r")
				{
					int sent_2me=strToInt(temp_msg.data.substr(proc._id + 1, 1));
					if(proc._comm.count<sent_2me)
						continue;
					else
						break;
				}
				else
				{
					l_delivered.push_back(temp_msg);
				}
				}

//			//wait receive
//			while (proc._comm.count < sent_2me)
//			{
//				sleep(1);
//			}
//
//			//deliver all messages
//			l_delivered = proc.wait_deliv_all(proc._tick_count);
			}
			//solve, only can receive request from other processes, then generate reply, but put them in l_defer
			Message delved_msg;
			Message tobe_reply;
			Message deferred_msg;
			while(l_delivered.size()>0)
			{
				delved_msg = l_delivered.front();
				l_delivered.pop_front();
				if (delved_msg.data.substr(0, 1) == "q")
				{
					//once received a request, current process need to update its highest_seq_no
					vector<string> req_data = split(delved_msg.data, ',');
					int seq_no_msg = strToInt(req_data[1]);	//seq_no of msg
					int proc_no_msg = strToInt(req_data[2]);//proc number of msg
					proc._highest_seq_no = max(proc._highest_seq_no,seq_no_msg);

					////get delay from l_reply, the msg.deliv is the delay
					//defer all replies
					deferred_msg.node=delved_msg.node;
					deferred_msg.data="p";
					deferred_msg.deliv=strToInt(l_reply.front()[3]);
					l_reply.pop_front();//remove the used information
					proc.l_defer.push_back(deferred_msg);
				}
				else
				{
					cout<<"I should not receive reply from other processes in my critical_section"<<endl;
				}
			}

			//send finish to server
			proc.send_finish();
		}
		else if(waiting_cs==1)
		{
			//next
			proc.update_tick_count();

			//c_s->0, send log release c_s, requesting_cs=false
			proc.send_log("Process " + intToStr(proc._id)+ " finished critical_section ");
			waiting_cs--;
			proc._requesting_cs=false;

			//send out all deferred reply, msg.deliv=now+deliv
			Message deferred_msg;
			while(proc.l_defer.size()>0)
			{
				deferred_msg=proc.l_defer.front();
				proc.l_defer.pop_front();
				deferred_msg.deliv=proc._tick_count+deferred_msg.deliv;
				proc.send_log(proc.reply(deferred_msg.node,deferred_msg.deliv));
				v_num_sent= proc.update_sent_vec(deferred_msg.node, v_num_sent);
			}

			//send num_sent to server
			proc.send_num_sent(v_num_sent);

//			//receive num_recv from server
//			sleep(5);
//			int sent_2me = proc.get_num_sent2me(proc._tick_count);
//
//			//wait receive
//			while (proc._comm.count < sent_2me)
//			{
//				sleep(1);
//			}

			while(true)
			{
				Message temp_msg;
				int temp_tag=proc.wait_recv(temp_msg,proc._tick_count);

				if(temp_tag<=-1)
				{
				if(temp_msg.data.substr(0, 1) == "r")
				{
					int sent_2me=strToInt(temp_msg.data.substr(proc._id + 1, 1));
					if(proc._comm.count<sent_2me)
						continue;
					else
						break;
				}
				else
				{
					l_delivered.push_back(temp_msg);
				}
				}
			}



			//solve, only can receive request from other processes, then generate reply, but put them in tobe_sent
			Message delved_msg;
			Message tobe_reply;
			while(l_delivered.size()>0)
			{
				delved_msg = l_delivered.front();
				l_delivered.pop_front();
				if (delved_msg.data.substr(0, 1) == "q")
				{
					//once received a request, current process need to update its highest_seq_no
					vector<string> req_data = split(delved_msg.data, ',');
					int seq_no_msg = strToInt(req_data[1]);	//seq_no of msg
					int proc_no_msg = strToInt(req_data[2]);//proc number of msg
					proc._highest_seq_no = max(proc._highest_seq_no,seq_no_msg);

					//get delay from l_reply, the msg.deliv=delay+now
					//generate all replies in l_tobe_sent
					tobe_reply.data="p";
					tobe_reply.deliv=strToInt(l_reply.front()[3])+proc._tick_count;
					l_reply.pop_front();//remove the used information
					l_tobe_sent.push_back(tobe_reply);
				}
				else
				{
					cout<<"I should not receive reply from other processes just after my critical_section"<<endl;
				}
			}

			//send finish to server
			proc.send_finish();
		}
		else
		{
			//next
			proc.update_tick_count();

			//In simple status, only need to send replies and requests
			//Fist, send all messages in the l_tobe_sent
			while (l_tobe_sent.size() > 0)
			{
				Message tobe_sent = l_tobe_sent.front();
				l_tobe_sent.pop_front();
				string temp_log = "Process " + intToStr(proc._id)
						+ " send reply to process " + intToStr(tobe_sent.node)
						+ "\n";
				cout << "Send reply to process " + intToStr(tobe_sent.node)
						<< endl;
				proc._comm.send(tobe_sent);
				proc.send_log(temp_log);
				proc.update_sent_vec(tobe_sent.node, v_num_sent);
			}

			//send requests, l_request remove front, seq_no=highest_seq_no+1, outstanding_reply=2
			Message tobe_request;
			if(strToInt(l_quest.front()[2])<=proc._tick_count)//judge if I will send request
			{
				//update proc status
				proc._requesting_cs=true;
				proc._outstanding_reply=2;
				proc._seq_no=proc._highest_seq_no+1;

				//send request to lower node
				tobe_request.node=proc.get_lower(proc._id);
				tobe_request.data="q,"+intToStr(proc._seq_no)+","+intToStr(proc._id);
				tobe_request.deliv=strToInt(l_quest.front()[3])+strToInt(l_quest.front()[2]);
				proc._comm.send(tobe_request);
				string temp_req_log1 = "Process " + intToStr(proc._id)
										+ " send request to process " + intToStr(tobe_request.node)
										+ "\n";
				proc.send_log(temp_req_log1);
				proc.update_sent_vec(tobe_request.node, v_num_sent);

				//send request to upper node
				tobe_request.node=proc.get_upper(proc._id);
				tobe_request.data="q,"+intToStr(proc._seq_no)+","+intToStr(proc._id);
				tobe_request.deliv=strToInt(l_quest.front()[3])+strToInt(l_quest.front()[2]);
				proc._comm.send(tobe_request);
				string temp_req_log2 = "Process " + intToStr(proc._id)
										+ " send request to process " + intToStr(tobe_request.node)
										+ "\n";
				proc.send_log(temp_req_log2);
				proc.update_sent_vec(tobe_request.node, v_num_sent);
			}

			//send notification to monitor, how many messages I sent
			proc.send_num_sent(v_num_sent);


			while(true)
			{
				Message temp_msg;
				int temp_tag=proc.wait_recv(temp_msg,proc._tick_count);

				if(temp_tag<=-1)
				{
				if(temp_msg.data.substr(0, 1) == "r")
				{
					int sent_2me=strToInt(temp_msg.data.substr(proc._id + 1, 1));
					if(proc._comm.count<sent_2me)
						continue;
					else
						break;
				}
				else
				{
					l_delivered.push_back(temp_msg);
				}
				}
			}

			//solve replies
			//if deliver request, highest_seq_no=max(highest_seq_no, q[]);
			//(1)put immediate replies in a list l_tobe_sent, the msg.deliv=now+1+deliv
			//(2)put deferred replies in a list proc.l_deffered, msg.deliv=delay,
			//if deliver reply
			//immediate outstanding_reply-1
			Message delved_msg;
			Message tobe_reply;
			Message defered_msg;
			while(l_delivered.size()>0)
			{
				delved_msg=l_delivered.front();
				l_delivered.pop_front();
				if (delved_msg.data.substr(0, 1) == "q")
				{
					//once received a request, current process need to update its highest_seq_no
					vector<string> req_data = split(delved_msg.data, ',');
					int seq_no_msg = strToInt(req_data[1]);	//seq_no of msg
					int proc_no_msg = strToInt(req_data[2]);//proc number of msg
					proc._highest_seq_no = max(proc._highest_seq_no,seq_no_msg);
					if(proc._requesting_cs && ((seq_no_msg > proc._seq_no)|| (seq_no_msg == proc._seq_no&& proc_no_msg > proc._id)))
					{//defer the reply
						defered_msg.data = "p";
						defered_msg.node = delved_msg.node;
						defered_msg.deliv = strToInt(l_reply.front()[3]);//the msg.deliv is just delay
						l_reply.pop_front();//remove the used information
						proc.l_defer.push_back(defered_msg);
					}
					else
					{
						defered_msg.data = "p";
						defered_msg.node = delved_msg.node;
						defered_msg.deliv = strToInt(l_reply.front()[3])+proc._tick_count;
						l_reply.pop_front();
						l_tobe_sent.push_back(defered_msg);
					}
				}

				else if (delved_msg.data.substr(0, 1) == "p")
				{
					proc._outstanding_reply=proc._outstanding_reply-1;
					if(proc._outstanding_reply==0)
					{
						proc.send_log("Process <"+intToStr(proc._id)+"> entering critical_section");
						waiting_cs=strToInt(l_cs.front()[3]);
						l_cs.pop_front();
					}
				}

				else
				{
					cout<<"unexpected message type   "+delved_msg.data.substr(0,1)+"  "<<endl;
				}

			}

			//send finish
			proc.send_finish();

		}
	}
	//termination
	proc.send_term();
	return 0;
}

